/* Copyright (c) 2024 Qualcomm Technologies International, Ltd. */
/* %%version */

#include "gatt_ots_client_common_util.h"
#include "gatt_ots_client_read_object.h"
#include "gatt_ots_client_abort.h"
#include "gatt_ots_client_con.h"

/*----------------------------------------------------------------------------*
 *  NAME
 *      OacpReadTimeOutHandler
 *
 *  DESCRIPTION
 *      If a control point operation times out, then no new control point operation
 *      shall be started by the Object Client until a new link is established with
 *      the Object Server. To ensure a positive user experience, if a control point
 *      operation times out, the Object Client should disconnect the physical link
 *      and then reconnect.OTS shall disconnect the L2CAP/LE-CoC connection and app
 *      is supposed to disconnect the ACL and reconnect back.
 *
 *  RETURNS
 *      void
 *
 *---------------------------------------------------------------------------*/
static void readObjectTimeOutHandler(uint16 val, GOTSC *const gattOtsClient)
{
    if (gattOtsClient->pendingCmd & OTSC_PENDING_OP_READ_OBJ_CONTENT)
    {
        /* Send Read procedure's failure message to app */
        OtscSendReadObjectCfm(gattOtsClient, 0, NULL, FALSE,
                              GATT_OTSC_RESULT_CODE_TIMEOUT,
                              GATT_SUPPLIER_OTS_CLIENT);

        /* TODO : Check whether remote supports the Abort feature before aborting
         * the read procedure.*/
        OtscSendAbortCommand(gattOtsClient);

        /*Reset the timer value */
        gattOtsClient->timerId = 0;

        GATT_OTS_CLIENT_INFO("readObjectTimeOutHandler : Read object procedure timedout !\n");
    }
    CSR_UNUSED(val);
}

/*******************************************************************************/
/*
    API to select the object at the OTS server device.
    Application should call this API only if OLCP feature & OACP -> Goto procedure features
    are set in feature supported value sent with Init CFM message.
*/

bool GattOtscReadObjectReq(ServiceHandle svcHandle,
                           uint32 offset,
                           uint32 length,
                           CsrBtConnId connId)
{
    GOTSC *gattOtsClient = ServiceHandleGetInstanceData(svcHandle);

    GATT_OTS_CLIENT_INFO("GattOtscReadObjectReq: svcHandle(0x%02x) offset[%d] length[%d]\n", svcHandle, offset, length);

    if (gattOtsClient)
    {
        if (gattOtsClient->pendingCmd & OTSC_PENDING_OP_READ_OBJ_CONTENT)
        {
            GATT_OTS_CLIENT_ERROR("GattOtscReadObjectReq : OACP procedure in progress !\n");
            return (FALSE);
        }
        else
        {
            OtscInternalMsgReadObject *message = CsrPmemAlloc(sizeof(*message));

            gattOtsClient->pendingCmd |= OTSC_PENDING_OP_READ_OBJ_CONTENT;

            message->svcHandle = gattOtsClient->srvcElem->service_handle;
            message->offset    = offset;
            message->length    = length;
            message->connId    = connId;

            GattOtscMessageSend(gattOtsClient->libTask,
                                OTSC_INTERNAL_MSG_READ_OBJ_REQ,
                                message);
            return (TRUE);
        }
    }
    else
    {
        GATT_OTS_CLIENT_ERROR("Invalid OTS Client instance !\n");
        return (FALSE);
    }
}

void OtscSendReadObjectCfm(GOTSC *const    gattOtsClient,
                           uint16          dataLength,
                           uint8          *data,
                           bool            moreToCome,
                           CsrBtResultCode result,
                           CsrBtSupplier   supplier)
{
    GattOtscReadObjectCfm *message = CsrPmemZalloc(sizeof(*message));

    message->svcHandle  = gattOtsClient->srvcElem->service_handle;
    message->dataLen    = dataLength;
    message->data       = data;
    message->moreToCome = moreToCome;
    message->resultCode = result;
    message->supplier   = supplier;

    /* Send the confirmation message to app task  */
    GattOtscMessageSend(gattOtsClient->appTask, GATT_OTSC_READ_OBJECT_CFM, message);

    if (gattOtsClient->pendingCmd & OTSC_PENDING_OP_READ_CHARS_FOR_OBJ_CONTENT)
    {
        /* Reset the pending cmd flag */
        gattOtsClient->pendingCmd &= ~OTSC_PENDING_OP_READ_CHARS_FOR_OBJ_CONTENT;
    }

    if (moreToCome == FALSE)
    {
        /* Reset the pending cmd flag */
        gattOtsClient->pendingCmd &= ~OTSC_PENDING_OP_READ_OBJ_CONTENT;

        /* Reset the current read object content info */
        memset(&gattOtsClient->readObjContentInfo, 0x00, sizeof(otscReadObjContentInfo));

        /* Stop the read object timer */
        if (gattOtsClient->timerId)
        {
            CsrSchedTimerCancel(gattOtsClient->timerId, NULL, NULL);
            gattOtsClient->timerId = 0;
        }

        /* Do not disconnect LE-COC channel before abortion if abort is requested */
        if (!(gattOtsClient->pendingCmd & OTSC_PENDING_OP_ABORT_PROCEDURE))
        {
            if (gattOtsClient->btConnId != CSR_BT_CONN_ID_INVALID)
            {
                /* Disconnect the L2CAP connections */
                CsrBtCml2caDisconnectReqSend(gattOtsClient->btConnId);
            }
        }

        GATT_OTS_CLIENT_INFO("GOTSC: OtscSendReadObjectCfm: svcHandle(0x%02x)\n", gattOtsClient->srvcElem->service_handle);
    }
}

void OtscHandleInternalMsgReadObject(GOTSC *gattOtsClient, OtscInternalMsgReadObject* msg)
{
    /* Reset if any old data still present */
    CsrMemSet(&gattOtsClient->readObjContentInfo, 0x00, sizeof(otscReadObjContentInfo));

    gattOtsClient->readObjContentInfo.offset = msg->offset;
    gattOtsClient->readObjContentInfo.length = msg->length;
    gattOtsClient->readObjContentInfo.connId = msg->connId;

    /* Set the pending op as we are going to read object's metadata */
    gattOtsClient->pendingCmd |= OTSC_PENDING_OP_READ_CHARS_FOR_OBJ_CONTENT;

    /* Read object contents involves three steps:
     * 1. Read object's property to check whether read operation is allowed.
     * 2. Get the object's size.
     * 3. Open the LE-COC/L2CAP channel if not already open
     * 4. Read object's content.
     */
    CsrBtGattReadReqSend(gattOtsClient->srvcElem->gattId,
                         gattOtsClient->srvcElem->cid,
                         gattOtsClient->deviceData.objPropertiesHandle,
                         0);
}

void OtscHandleReadCfmToReadObjectContent(GOTSC *const gattOtsClient, const CsrBtGattReadCfm *cfm)
{
    if (cfm->resultCode == CSR_BT_GATT_RESULT_SUCCESS)
    { /* readCfm->handle is objPropertiesHandle */
        if (cfm->handle == gattOtsClient->deviceData.objPropertiesHandle)
        {
            uint32 *propertyVal = (uint32 *) CsrPmemAlloc(cfm->valueLength);
            CsrMemCpy(propertyVal, cfm->value, cfm->valueLength);

            if (*propertyVal & GATT_OTSC_OBJ_PROPERTY_READ)
            { /* Read operation allowed for the selected object */
                /* Read the object's size now */
                CsrBtGattReadReqSend(gattOtsClient->srvcElem->gattId,
                                     gattOtsClient->srvcElem->cid,
                                     gattOtsClient->deviceData.objSizeHandle,
                                     0);
            }
            else
            {
                OtscSendReadObjectCfm(gattOtsClient, 0, NULL, FALSE,
                                      GATT_OTSC_RESULT_CODE_PROCEDURE_NOT_PERMITTED,
                                      GATT_SUPPLIER_OTS_CLIENT);
            }
            CsrPmemFree(propertyVal);
        }
        else
        { /* readCfm->handle is objSizeHandle */
            /* Continue read operation only if it is not aborted */
            if (gattOtsClient->pendingCmd & OTSC_PENDING_OP_ABORT_PROCEDURE &&
                gattOtsClient->pendingCmd & OTSC_PENDING_OP_READ_OBJ_CONTENT)
            {
                 /* Send abort procedure's failure message to app */
                 OtscSendReadObjectCfm(gattOtsClient, 0, NULL, FALSE,
                                       GATT_OTSC_RESULT_CODE_OPERATION_ABORTED,
                                       GATT_SUPPLIER_OTS_CLIENT);
                 GATT_OTS_CLIENT_ERROR("GOTSC : Read operation is requested to abort !");
            }
            else
            {
                uint32 requestedSizeToRead;
                uint32 offset      = gattOtsClient->readObjContentInfo.offset;
                uint32 length      = gattOtsClient->readObjContentInfo.length;
                uint32 *objCurSize = CsrPmemZalloc(OTSC_OBJECT_SIZE);
                CsrMemCpy(objCurSize, cfm->value, OTSC_OBJECT_SIZE);

                GATT_OTS_CLIENT_INFO("GOTSC: Object size received : objectsize(%d) offset(%d) length(%d)  !\n", *objCurSize, offset, length);

                /* Reset the pending opcode flag set to read metadata */
                gattOtsClient->pendingCmd &= ~OTSC_PENDING_OP_READ_CHARS_FOR_OBJ_CONTENT;

                /* App sends the length as zero if complete object to be read */
                if (length == 0)
                {
                    requestedSizeToRead = length = *objCurSize;
                }
                else
                {
                    requestedSizeToRead = offset + length;
                }

                if (requestedSizeToRead <= *objCurSize)
                {
                    /* save the object size being requested */
                    gattOtsClient->readObjContentInfo.length = length;

                    /* Reset the field for no chance of miscalculation later */
                    gattOtsClient->readObjContentInfo.objSizeRcvd = 0;

                    /* Initiate OTS L2cap connection if not already open */
                    if (gattOtsClient->btConnId == CSR_BT_CONN_ID_INVALID)
                    {
                        if (!OtscInitiateOtsConnection(gattOtsClient))
                        {
                            OtscSendReadObjectCfm(gattOtsClient, 0, NULL, FALSE,
                                              GATT_OTSC_RESULT_CODE_CHANNEL_UNAVAILABLE,
                                              GATT_SUPPLIER_OTS_CLIENT);
                            GATT_OTS_CLIENT_ERROR("GOTSC: Connection attempt failed !");
                        }
                    }
                    else
                    {
                        /* Channel is already open; just go for object transfer */
                        OtscHandleOtsConnectCfm(gattOtsClient);
                    }
                }
                else
                {
                    OtscSendReadObjectCfm(gattOtsClient, 0, NULL, FALSE,
                                          GATT_OTSC_RESULT_CODE_INVALID_PARAMETER,
                                          GATT_SUPPLIER_OTS_CLIENT);
                    GATT_OTS_CLIENT_ERROR("GOTSC : Requested size is beyond of object size !");
                }

                CsrPmemFree(objCurSize);
            }
        }
    }
    else
    {
        OtscSendReadObjectCfm(gattOtsClient, 0, NULL, FALSE,
                              cfm->resultCode, cfm->resultSupplier);
    }
}

void OtscHandleOtsConnectCfm(GOTSC *const gattOtsClient)
{
    if (gattOtsClient->pendingCmd & OTSC_PENDING_OP_READ_OBJ_CONTENT)
    {
        /* OTS channel is created as part of read object content procedure.
         * prepare the body to send read request to remote, i.e.,
         * read opcode(1 octet) + offset (4 octets) + length (4 octets)*/
        uint8  valueSize = sizeof(uint8) + sizeof(uint32) + sizeof(uint32);
        uint8* value     = CsrPmemZalloc(valueSize);

        value[0]  = GATT_OTSC_OBJ_ACTION_OP_READ;
        memcpy(&value[1], &gattOtsClient->readObjContentInfo.offset, OTSC_OBJECT_SIZE);
        memcpy(&value[5], &gattOtsClient->readObjContentInfo.length, OTSC_OBJECT_SIZE);

        /* Write "Read opcode" to OACP characteristic */
        CsrBtGattWriteReqSend(gattOtsClient->srvcElem->gattId,
                              gattOtsClient->srvcElem->cid,
                              gattOtsClient->deviceData.objActionControlPointHandle,
                              0,
                              valueSize,
                              value);
    }
    else
    {
        /*App initiated connection, just send the CFM message back to app */
        OtscSendOtsChannelOpenedCfm(gattOtsClient,
                                    gattOtsClient->btConnId,
                                    GATT_OTSC_RESULT_CODE_SUCCESS,
                                    GATT_SUPPLIER_OTS_CLIENT);
    }
}

void OtscHandleWriteCfmToReadObjectContent(GOTSC *const gattOtsClient,
                                           const CsrBtGattWriteCfm *cfm)
{
    if (cfm->resultCode == CSR_BT_GATT_RESULT_SUCCESS &&
        cfm->resultSupplier == CSR_BT_SUPPLIER_GATT)
    {
        /* Start OACP timer and wait for OACP indication */
        OtscStartOacpTimer(gattOtsClient);
    }
    else
    {
        /* Inform app about the failure */
        OtscSendReadObjectCfm(gattOtsClient, 0, NULL, FALSE,
                              cfm->resultCode,
                              cfm->resultSupplier);
    }
}

void OtscHandleObjActionControlPointInd(GOTSC *const gattOtsClient,
                                        const CsrBtGattClientIndicationInd *ind)
{
    GattOtscResultCode result = GATT_OTSC_RESULT_CODE_UNKNOWN;

    if (ind->valueLength > 0)
    {
        uint8 responseCode    = ind->value[0];
        uint8 requestedOpcode = ind->value[1];
        uint8 resultCode      = ind->value[2];

        if (responseCode == GATT_OTSC_OBJ_ACTION_OP_RESPONSE_CODE)
        {
           /* Map the remote's response with the local OTS result code */
            switch (resultCode)
            {
                case 0x01:
                {
                    result = GATT_OTSC_RESULT_CODE_SUCCESS;
                    break;
                }
                case 0x02:
                {
                    result = GATT_OTSC_RESULT_CODE_OPCODE_NOT_SUPPORTED;
                    break;
                }
                case 0x03:
                {
                    result = GATT_OTSC_RESULT_CODE_INVALID_PARAMETER;
                    break;
                }
                case 0x04:
                {
                    result = GATT_OTSC_RESULT_CODE_INSUFFICIENT_RESOURCES;
                    break;
                }
                case 0x05:
                {
                    result = GATT_OTSC_RESULT_CODE_INVALID_OBJECT;
                    break;
                }
                case 0x06:
                {
                    result = GATT_OTSC_RESULT_CODE_CHANNEL_UNAVAILABLE;
                    break;
                }
                case 0x07:
                {
                    result = GATT_OTSC_RESULT_CODE_UNSUPPORTED_TYPE;
                    break;
                }
                case 0x08:
                {
                    result = GATT_OTSC_RESULT_CODE_PROCEDURE_NOT_PERMITTED;
                    break;
                }
                case 0x09:
                {
                    result = GATT_OTSC_RESULT_CODE_OBJECT_LOCKED;
                    break;
                }
                case 0x0A:
                {
                    result = GATT_OTSC_RESULT_CODE_OPERATION_FAILED;
                    break;
                }
                default:
                {
                    break;
                }
            }
        }

        if (gattOtsClient->timerId)
        {
            CsrSchedTimerCancel(gattOtsClient->timerId, NULL, NULL);
            gattOtsClient->timerId = 0;
        }

        if (requestedOpcode == GATT_OTSC_OBJ_ACTION_OP_READ)
        {
            if (result == GATT_OTSC_RESULT_CODE_SUCCESS)
            {
                /*Start read object timer and refresh it for each MMD we receive */
                gattOtsClient->timerId = OTSC_START_READ_OBJECT_TIMER(gattOtsClient);
            }
            else
            {
                if (gattOtsClient->pendingCmd & OTSC_PENDING_OP_READ_OBJ_CONTENT)
                {
                    /* Send read object failure to application */
                    OtscSendReadObjectCfm(gattOtsClient, 0, NULL, FALSE,
                                          result,
                                          GATT_SUPPLIER_OTS_CLIENT);
                }
                else
                {
                    /* Inform app about other OACP procedures failure */
                    GATT_OTS_CLIENT_ERROR("GOTSC : OACP ind received for unknown Read request !\n");
                }
            }
        }
        else if (requestedOpcode == GATT_OTSC_OBJ_ACTION_OP_ABORT)
        {
            if (gattOtsClient->pendingCmd & OTSC_PENDING_OP_ABORT_PROCEDURE)
            {
                /* Inform app about the current abort procedure result */
                OtscSendAbortCfm(gattOtsClient, result, GATT_SUPPLIER_OTS_CLIENT);
            }
            else
            {
                /* Inform app about other OACP procedures failure */
                GATT_OTS_CLIENT_ERROR("GOTSC : OACP ind received for unknown Abort request !\n");
            }
        }
        else
        {
            GATT_OTS_CLIENT_ERROR("GOTSC : OACP ind received for unknown opcode !\n");
        }
    }
}

void OtscPrepareAndSendObjectContent(GOTSC *const gattOtsClient,
                                     uint8* content,
                                     uint16 length)
{
    uint32 objRcvd  = gattOtsClient->readObjContentInfo.objSizeRcvd;
    uint32 objToRcv = gattOtsClient->readObjContentInfo.length;

    /* What if any rogue server sends the data beyond the size requested */
    if ((objRcvd + length) > objToRcv)
    {
        /* receive only the data size we requested */
        length = objToRcv - objRcvd;
    }

    gattOtsClient->readObjContentInfo.objSizeRcvd += length;

    objRcvd = gattOtsClient->readObjContentInfo.objSizeRcvd;

    /* Check whether we have received complete object content as requested */
    if (objRcvd < objToRcv)
    {
        OtscSendReadObjectCfm(gattOtsClient, length, content, TRUE,
                              GATT_OTSC_RESULT_CODE_SUCCESS,
                              GATT_SUPPLIER_OTS_CLIENT);

        /* Restart read content timer to track next packet arrival */
        CsrSchedTimerCancel(gattOtsClient->timerId, NULL, NULL);
        gattOtsClient->timerId = OTSC_START_READ_OBJECT_TIMER(gattOtsClient);
    }
    else
    {
        /* Requested size of object content has been received */
        OtscSendReadObjectCfm(gattOtsClient, length, content, FALSE,
                              GATT_OTSC_RESULT_CODE_SUCCESS,
                              GATT_SUPPLIER_OTS_CLIENT);
        GATT_OTS_CLIENT_ERROR("GOTSC : Object transfer completed !\n");
    }
}

#ifndef CSR_STREAMS_ENABLE
void OtscHandleCmDataInd(GOTSC *const gattOtsClient, CsrBtCmL2caDataInd *ind)
{
    OtscPrepareAndSendObjectContent(gattOtsClient, ind->payload, ind->length);
    CsrBtCmL2caDataResSend(ind->btConnId);
}
#endif

